C# Toolkits
Toolkits are the primary way to extend HPD-Agent with custom capabilities. A tool container is a class that contains one or more capabilities that the agent can use to accomplish tasks.
The Three Capability Types
HPD-Agent supports three types of capabilities, each suited for different use cases:
| Type | Purpose | Container? | Use When |
|---|---|---|---|
| AIFunction | Single, focused operation | No | One action, clear inputs/outputs |
| Skill | Multi-function workflow | Yes | Grouped functions with shared instructions |
| SubAgent | Delegated agent | No | Complex tasks requiring autonomous reasoning |
AIFunction
A single function the agent can call. Best for discrete operations with clear inputs and outputs.
public class WeatherTools
{
[AIFunction]
[AIDescription("Get the current weather for a location")]
public async Task<string> GetWeather(
[AIDescription("City name (e.g., 'London', 'New York')")] string city)
{
// Implementation
}
}→ See 02.1.1 AIFunctions.md for details.
Skill
A group of related functions with shared instructions. When activated, the skill expands to reveal its constituent functions.
public class SearchTools
{
[Skill]
public Skill ResearchSkill()
{
return SkillFactory.Create(
name: "Research",
description: "Deep research across multiple sources",
// Auto-generated: "Research skill activated. Available functions: WebSearch, CodeSearch"
functionResult: null, // Auto-generated message is sufficient
systemPrompt: "1. Search web first\n2. Search code if relevant\n3. Synthesize findings",
"SearchTool.WebSearch", "SearchTool.CodeSearch"
);
}
}→ See 02.1.2 Skills.md for details.
SubAgent
A child agent that can be delegated tasks. SubAgents have their own configuration, Toolkits, and conversation thread.
public class DelegationTools
{
[SubAgent]
public SubAgent CodeReviewer()
{
return SubAgentFactory.Create(
name: "Code Reviewer",
description: "Reviews code for quality and best practices",
agentConfig: new AgentConfig { SystemInstructions = "You are a code reviewer..." },
typeof(FileSystemTools)
);
}
}→ See 02.1.3 SubAgents.md for details.
Basic Registration
Register tools with the AgentBuilder:
var agent = await new AgentBuilder()
.WithToolkit<WeatherTools>()
.WithToolkit<SearchTools>()
.BuildAsync();With Dependency Injection
If your tool requires services, use WithServiceProvider():
var services = new ServiceCollection()
.AddSingleton<IWeatherService, WeatherService>()
.BuildServiceProvider();
var agent = await new AgentBuilder()
.WithServiceProvider(services)
.WithToolkit<WeatherTools>()
.BuildAsync();With Tool Metadata
Pass runtime context to control dynamic behavior:
var agent = await new AgentBuilder()
.WithToolkit<SearchTools>(new SearchMetadata
{
ProviderName = "Tavily",
HasAdvancedFeatures = true
})
.BuildAsync();→ See 02.1.4 Tool Metadata.md for details.
Which Tool Type Should I Use?
The Core Question: How Much Autonomy Does the Task Need?
| AIFunction | Skill | SubAgent | MultiAgent Workflow | |
|---|---|---|---|---|
| Autonomy | None — executes one thing | Guided — follows your steps | Autonomous — figures it out | Orchestrated — conditional pipeline |
| Scope | Single deterministic operation | Multi-step workflow, parent thread | Full agent loop, separate thread | Multiple agents with routing logic |
| Tools | Inherits parent's | References from parent's | Own isolated tool set | Each node has its own agent |
| Memory | None | None | Optional (stateful/per-session) | Shared workflow data |
| Cost | Cheapest (no extra LLM calls) | Cheap (uses parent's LLM call) | Expensive (own LLM calls per invocation) | Most expensive |
The Decision
Ask yourself these questions in order:
Is it a single, deterministic operation?
├── Yes → AIFunction
└── No
├── Can you enumerate the steps upfront?
│ └── Yes → Skill
└── No
├── Is it one complex task that needs autonomous reasoning?
│ └── Yes → SubAgent
└── Does it need multiple specialized agents, parallel execution,
or conditional routing between distinct stages?
└── Yes → MultiAgent WorkflowExamples
| Task | Capability | Why |
|---|---|---|
| Get weather for a city | AIFunction | Single operation, clear I/O |
| Send an email | AIFunction | Single operation |
| Research a topic | Skill | Web search + doc search + synthesize — steps are known |
| Review code for style | Skill | Read → analyze → format — steps are known |
| Debug a complex issue | SubAgent | Needs to autonomously reason through unknown steps |
| Audit code for security | SubAgent | Specialized domain, unpredictable path |
| Triage → route to specialist → summarize | MultiAgent | Distinct stages, conditional routing |
| Run research and writing in parallel | MultiAgent | Parallel execution across agents |
The Isolation Benefit of SubAgents and MultiAgent
SubAgents and MultiAgent nodes don't just add autonomy — they run in isolated execution environments. This matters in several ways:
Context isolation: Each SubAgent or workflow node gets a clean context window. It doesn't inherit the parent's conversation history, accumulated tool calls, or injected system prompts. This means:
- A SubAgent is not distracted by 30 turns of unrelated prior conversation
- A workflow node only receives the structured output of the previous node, not its full reasoning trace
Context compression: When a task requires a lot of intermediate work — reading many files, calling many tools, multi-step reasoning — all of that churn accumulates in context. A SubAgent discards all intermediate context after finishing. The parent only receives the final result:
Parent asks SubAgent: "research this topic"
SubAgent does: 10 searches, reads 5 docs, 20 tool calls ← throwaway context
Parent receives: one clean summary string ← only this staysVersus doing it inline: all 20 tool calls sit in parent history forever, consuming tokens on every subsequent LLM call.
Model isolation: SubAgents and MultiAgent nodes can use a different (cheaper or specialized) model than the parent. Expensive parent model for orchestration, cheap model for simple sub-tasks.
Tool isolation: SubAgents only have access to the tools you explicitly give them — no risk of using the wrong tool from the parent's broader set.
Use a SubAgent or MultiAgent node not just when you need autonomy, but when the task generates intermediate tokens you don't need to carry forward, or when you want to run it with a different model or tool set.
Common Traps
| Trap | Problem | Fix |
|---|---|---|
| Using SubAgent when steps are known | Pays extra LLM calls for nothing | Use Skill instead |
| Using AIFunction for branching logic | Function body gets complicated | Use Skill with workflow instructions |
| Doing heavy research inline in parent | All intermediate tokens sit in parent history forever | Delegate to SubAgent |
| Using MultiAgent when one SubAgent would do | Unnecessary complexity | MultiAgent is for parallel execution or conditional routing between distinct agents |
Using [Collapse] to solve context bloat from long tasks | Collapse hides definitions, not history | Delegate long tasks to SubAgent instead |
Tool Collapsing
When you have many tools, the agent's context window can become cluttered. HPD-Agent supports collapsing Toolkits into containers that expand on demand.
Before expansion: After expansion:
┌──────────────────┐ ┌──────────────────┐
│ SearchTools │ ──► │ WebSearch │
│ (3 functions) │ │ CodeSearch │
└──────────────────┘ │ DocSearch │
└──────────────────┘→ See 02.1.5 Context Engineering.md for details.
Writing Instructions
Skills, SubAgents, and collapsed tools all use dual-context instructions to guide the agent:
| Parameter | Location | Lifetime | Use For |
|---|---|---|---|
functionResult | Conversation history | One-time | Additional context (appended to auto-generated message) |
systemPrompt | System prompt | Every turn | Critical rules, workflow |
Important: The system automatically generates a base activation/expansion message:
"{Name} skill activated. Available functions: {FunctionList}" "{ToolkitName} expanded. Available functions: {FunctionList}"Your
functionResultis appended to this auto-generated message. Don't duplicate the activation info—use it only for additional context. Passnullif the auto-generated message is sufficient.
// Skill instructions - dual-context
SkillFactory.Create(
name: "Research",
description: "Deep research",
// Auto-generated: "Research skill activated. Available functions: WebSearch, CodeSearch"
functionResult: null, // Auto-generated message is sufficient
systemPrompt: @"
1. Search web first
2. Search code if relevant
3. Synthesize findings", // Persistent
...
);
// Toolkit instructions - same dual-context
[Collapse(
"Database operations",
// Auto-generated: "DatabaseTools expanded. Available functions: Query, Insert, Update, Delete"
FunctionResult: "Connected to: production_db", // Additional context only
SystemPrompt: "CRITICAL: Never DELETE without WHERE clause"
)]→ See 02.1.6 Writing Instructions.md for best practices.
Conditional Visibility
Show or hide capabilities based on runtime metadata using simple expressions:
// Simple boolean
[ConditionalFunction("HasAdvancedFeatures")]
// Comparison
[ConditionalFunction("ProviderCount > 1")]
// Boolean logic
[ConditionalFunction("HasTavily || HasBrave")]
// On parameters too
public Task<string> Search(
string query,
[ConditionalParameter("ProviderCount > 1")] string? provider = null)→ See 02.1.7 Conditional Expression DSL.md for the full syntax.
When to Upgrade Your Capability Type
As your application grows, you may find that existing capabilities need to evolve. Use these signals to know when to refactor:
AIFunction → Skill
Upgrade when your AIFunction implementation starts looking like a workflow:
// Warning sign: function orchestrating other functions
[AIFunction]
public async Task<string> ResearchTopic(string topic)
{
var web = await WebSearch(topic); // calling other tools
var code = await CodeSearch(topic); // inside one function
return Synthesize(web, code); // fixed pipeline
}
// Better: Skill lets the agent use the tools flexibly with guidance
[Skill]
public Skill ResearchSkill() => SkillFactory.Create(
name: "Research",
description: "Deep research across web and code",
functionResult: null,
systemPrompt: "1. Search web first\n2. Search code if relevant\n3. Synthesize findings",
"SearchTools.WebSearch", "SearchTools.CodeSearch"
);Signals to upgrade:
- The function calls other AIFunctions internally
- The logic has branching based on intermediate results
- You find yourself wanting to give the agent more flexibility in how it executes the steps
Skill → SubAgent
Upgrade when the workflow can't be enumerated upfront or needs its own model/tools:
// Warning sign: Skill instructions are getting very long and conditional
systemPrompt: @"
IF web search returns no results, try code search.
IF code search also fails, try documentation.
IF documentation doesn't help, try asking clarifying questions.
IF the topic is security-related, also check CVE database.
..." // you're writing an agent in a string
// Better: SubAgent with its own reasoning
[SubAgent]
public SubAgent Researcher() => SubAgentFactory.Create(
name: "Research",
description: "Researches topics autonomously",
agentConfig: new AgentConfig { SystemInstructions = "You are a research expert..." },
typeof(SearchTools), typeof(DocumentTools)
);Signals to upgrade:
- Skill instructions keep growing with more conditionals and edge cases
- The task path genuinely varies based on what the agent finds
- You want to use a different (cheaper or specialized) model for this task
- The task generates a lot of intermediate tokens you don't want in the parent's history
SubAgent → MultiAgent Workflow
Upgrade when you have multiple distinct stages, parallel work, or need conditional routing between specialized agents:
// Warning sign: SubAgent doing too many unrelated jobs
[SubAgent]
public SubAgent DoEverything() => SubAgentFactory.Create(
name: "All Tasks",
description: "Triages, researches, writes, and reviews",
agentConfig: new AgentConfig { SystemInstructions = "Do all the things..." },
typeof(TriageTools), typeof(ResearchTools), typeof(WritingTools), typeof(ReviewTools)
);
// Better: MultiAgent workflow with specialized nodes
var workflow = new MultiAgent()
.AddAgent("triage", triageAgent)
.AddAgent("researcher", researchAgent)
.AddAgent("writer", writerAgent)
.AddAgent("reviewer", reviewAgent)
.From("triage").To("researcher").WhenEquals("type", "research")
.From("triage").To("writer").WhenEquals("type", "writing")
.From("writer").To("reviewer")
.BuildAsync();Signals to upgrade:
- One SubAgent is doing clearly distinct jobs that could be specialized
- You need parallel execution (research and writing happening simultaneously)
- You need conditional routing (different paths based on task type)
- Different stages need different models or tool sets
Next Steps
- 02.1.1 AIFunctions.md - Single-function capabilities
- 02.1.2 Skills.md - Multi-function workflows
- 02.1.3 SubAgents.md - Delegated agents
- 02.1.4 Tool Metadata.md - Dynamic descriptions and conditionals
- 02.1.5 Context Engineering.md - Collapsing and context management
- 02.1.6 Writing Instructions.md - How to write effective instructions
- 02.1.7 Conditional Expression DSL.md - Expression syntax for conditional visibility